// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// In this file:
// Parser helpers/utils

// In ~sync up to:
// bba6fb4199df by Luuk van Dijk on Dec 14, 2011

/*
WIP: Package parser produces ASTs from Go source code.

The Node field

Most AST nodes produced by the parser have a 'Node interface{}' field. The
parser package doesn't use/fill this field. It is intended to be used by a
typechecker, compiler, code generator, ...

For a possible use case inspiration/example lookup the struct Node type in
http://code.google.com/p/go/source/browse/src/cmd/gc/go.h

AST nodes guaranteed to be a simple terminal node (INT, FLOAT, IMAG, STR) don't
have the Node field. Benchmarks currently show a nearly 50% slowdown of the
parser if the Node field is included with them.

*/
package parser

import (
	"container/list"
	"fmt"
	"github.com/cznic/strutil"
	goscanner "go/scanner"
	"path"
	"reflect"
	"runtime"
	"strconv"
	"strings"
)

// Comm direction in OTCHAN
const (
	Cboth = iota
	Csend
	Crecv
)

func colas(left, right *list.List) (as interface{}) {
	as = OAS2{List: left, Rlist: right, Colas: true}
	return
}

func count(l *list.List) (n int) {
	if l != nil {
		n = l.Len()
	}
	return
}

func concat(a, b *list.List) *list.List {
	if a == nil {
		return b
	}

	if b != nil {
		a.PushBackList(b)
	}

	return a
}

func list1(n interface{}) (l *list.List) {
	if n == nil {
		return
	}

	if x, ok := n.(OBLOCK); ok && !x.Compound {
		return x.List
	}

	l = list.New()
	l.PushBack(n)
	return
}

func lst(l *list.List, n interface{}) *list.List {
	return concat(l, list1(n))
}

func liststmt(l *list.List) (n interface{}) {
	return OBLOCK{List: l}
}

func compoundstmt(l *list.List) (n interface{}) {
	return OBLOCK{Compound: true, List: l}
}

// Import captures an import clause.
type Import struct {
	*Sym             // import as
	Lit  interface{} // Import path
}

func (i *Import) String() string {
	if i.Sym != nil {
		return fmt.Sprintf("import %s %q", i.Sym, i.Lit)
	}

	return fmt.Sprintf("import %q", i.Lit)
}

func (i *Import) str(f *formatter) {
	if i.Sym != nil {
		f.format("%s %q", i.Sym, i.Lit)
		return
	}

	f.format("%q", i.Lit)
}

// Imports is a slice of Import items.
type Imports []Import

func (i Imports) String() string {
	a := make([]string, len(i))
	for ix, x := range i {
		a[ix] = x.String()
	}
	return strings.Join(a, "\n")
}

func nodeName(node interface{}) string {
	return strings.Split(fmt.Sprintf("%T", node), ".")[1]
}

// UNARY captures the operand of an unary operator.
// It's only the underlying type for some other nodes.
type UNARY struct {
	Node interface{} // See the package comment about this field
	Left interface{}
}

func (u UNARY) un(f *formatter, op string) {
	f.format(op)
	u.Left.(sourcer).src(f)
}

func chkmethod(yylex *lexer, pos Pos, rcvrs *list.List, name *Sym, args, fnres *list.List) interface{} {
	var rcvr interface{}
	switch {
	case rcvrs == nil || rcvrs.Len() == 0:
		yylex.error(pos, "no receiver")
		rcvr = OKEY{Right: ONONAME{Sym: &Sym{Pos: pos, Name: []byte("<missing>")}}}
	case rcvrs.Len() != 1:
		yylex.error(pos, "multiple receiver")
		fallthrough
	default:
		rcvr = rcvrs.Front().Value
	}
	switch x := rcvr.(type) {
	case OKEY:
		right := x.Right
		if oind, ok := right.(OIND); ok {
			right = oind.Type
		}
		if paren, fault := right.(OTPAREN); fault {
			yylex.error(paren.Pos, "cannot parenthesize receiver type")
		}
	case OTPAREN:
		yylex.error(x.Pos, "cannot parenthesize receiver type")
	case OIND:
		if paren, fault := x.Type.(OTPAREN); fault {
			yylex.error(paren.Pos, "cannot parenthesize receiver type")
		}
	}
	name.Type = OTFUNC{Left: rcvr, List: args, Rlist: fnres, NoFunc: true}
	return ODCLFUNC{Name: name}
}

// File is the product of ParsePkgFile
type File struct {
	Name         string      // Package name
	Imports      Imports     // Imports
	ImportCounts []int       // Grouping of imports in original source
	Ast          interface{} // Root
}

func (l *lexer) parsePkgFile(xgo bool) (f *File, err error) {
	l.xgo = xgo
	f = &File{}

	defer func() {
		if e := recover(); e != nil {
			err = e.(error)
			l.Error(err.Error())
			err = l.errors.GetError(goscanner.Sorted)
		}
	}()

	result := yyParse(l)
	f.Name, f.Imports, f.ImportCounts, f.Ast = l.pkgname, l.imports, l.importCounts, l.xtop
	if result != 0 || l.errors.ErrorCount() != 0 {
		err = l.errors.GetError(goscanner.Sorted)
	}
	return
}

// ParsePkgFile parses a Go source code in src, assuming it comes from filename,
// xgo enables experimental language extensions.
// String literal values are in-place escape processed, so src is possibly  modified.
// The function returns a File or an Error if any. 
// A non-nil error is possibly a sorted go/scanner.ErrorList.
func ParsePkgFile(filename string, src []byte, xgo bool) (f *File, err error) {
	return newLexer(filename, src).parsePkgFile(xgo)
}

// Out pretty prints an AST 'item' to 'f', prefixed with 'pre'.
// If the item wraps another AST nodes they are outputed recursively also.
// This function prints the AST form, not the source form.
func Out(f strutil.Formatter, pre string, item interface{}) {

	var out func(string, reflect.Value)

	out = func(pre string, v reflect.Value) {
		switch x := v; x.Kind() {

		default:
			panic(fmt.Errorf("internal error (%s) pre=%q: '%T'(kind%d=%s) %+v\n", me(), pre, x, int(x.Kind()), x.Kind(), x))

		case reflect.Bool:
			if v := x.Bool(); v {
				f.Format("%s%t\n", pre, v)
			}

		case reflect.Interface:
			if !x.IsNil() {
				out(pre, reflect.ValueOf(x.Interface()))
			}

		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			if v := x.Int(); v != 0 {
				f.Format("%s%d\n", pre, v)
			}

		case reflect.String:
			if v := x.String(); v != "" {
				f.Format("%s%q\n", pre, v)
			}

		case reflect.Ptr:
			if lst, ok := x.Interface().(*list.List); ok {
				if lst == nil {
					break
				}

				f.Format("%s%T[%d]%i\n", pre, lst, lst.Len())
				for node, i := lst.Front(), 0; node != nil; node, i = node.Next(), i+1 {
					out(fmt.Sprintf("%d:", i), reflect.ValueOf(node.Value))
				}
				f.Format("%u")
				break

			}

			if !x.IsNil() {
				out(pre+"*", x.Elem())
			}

		case reflect.Slice:
			switch s := x.Interface().(type) {
			default:
				panic(fmt.Errorf("internal error (%s) %T %+v\n", me(), pre, s, s))
			case []byte:
				f.Format("%s%q\n", pre, s)
			case STR:
				f.Format("%s%T:%q\n", pre, s, s)
			case Token:
				f.Format("%s%T:%q\n", pre, s, s)
			}

		case reflect.Struct:
			if v, ok := x.Interface().(Pos); ok { // save a bit of vertical space
				f.Format("%s%T:\"%s:%d:%d\"\n", pre, v, v.Name, v.Line, v.Col)
				break
			}

			f.Format("%s%s%i\n", pre, x.Type().String())
			for i := 0; i < x.NumField(); i++ {
				out(x.Type().Field(i).Name+":", x.Field(i))
			}
			f.Format("%u")
		}
	}

	out(pre, reflect.ValueOf(item))
}

type formatter struct {
	strutil.Formatter
}

func (f *formatter) format(format string, args ...interface{}) {
	if _, err := f.Formatter.Format(format, args...); err != nil {
		panic(err)
	}
}

type sourcer interface {
	src(f *formatter)
}

type lister struct {
	preFirst string
	pre      string
	*list.List
	empty    string
	postLast string
}

func (l lister) src(f *formatter) {
	if l.List == nil {
		f.format(l.empty)
		return
	}

	for first, node := true, l.List.Front(); node != nil; node = node.Next() {
		if first {
			f.format(l.preFirst)
			first = false
		} else {
			f.format(l.pre)
		}

		n := 0
		switch x := node.Value.(type) {
		case OVAR:
			if x.Names == nil {
				f.format("var ()")
				continue
			}

			if n = x.SrcJoin; n == 0 {
				break
			}

			f.format("var (%i")
			for i := 0; i < n; i++ {
				x.src2(f, "\n")
				if i+1 < n {
					node = node.Next()
					x = node.Value.(OVAR)
				}
			}
			f.format("%u\n)")
			continue
		case OCONST:
			if x.Names == nil {
				f.format("const ()")
				continue
			}

			if n = x.SrcJoin; n == 0 {
				break
			}

			f.format("const (%i")
			for i := 0; i < n; i++ {
				x.src2(f, "\n")
				if i+1 < n {
					node = node.Next()
					x = node.Value.(OCONST)
				}
			}
			f.format("%u\n)")
			continue
		case ODCLTYPE:
			if x.Sym == nil {
				f.format("type ()")
				continue
			}

			if n = x.SrcJoin; n == 0 {
				break
			}

			f.format("type (%i")
			var x2 interface{} = x
			for i := 0; i < n; i++ {
				switch x3 := x2.(type) {
				case ODCLTYPE:
					x3.src2(f, "\n")
				case OXGO:
					x3.src2(f, "\n")
				}
				if i+1 < n {
					node = node.Next()
					x2 = node.Value
				}
			}
			f.format("%u\n)")
			continue
		case OXGO:
			if n = x.SrcJoin; n == 0 {
				break
			}

			f.format("type (%i")
			var x2 interface{} = x
			for i := 0; i < n; i++ {
				switch x3 := x2.(type) {
				case ODCLTYPE:
					x3.src2(f, "\n")
				case OXGO:
					x3.src2(f, "\n")
				}
				if i+1 < n {
					node = node.Next()
					x2 = node.Value
				}
			}
			f.format("%u\n)")
			continue
		}

		if x, ok := node.Value.(sourcer); ok {
			x.src(f)
			continue
		}

		panic(fmt.Errorf("%T is not a 'sourcer'", node.Value))
	}
	f.format(l.postLast)
}

func (file *File) src(f *formatter) {
	f.format("package %s\n", file.Name)
	i := 0
	for _, n := range file.ImportCounts {
		switch n {
		case -1:
			f.format("\nimport ")
			file.Imports[i].str(f)
			i++
		case 0:
			f.format("\nimport ()")
		default:
			f.format("\nimport (%i")
			for j := 0; j < n; j++ {
				f.format("\n")
				file.Imports[i].str(f)
				i++
			}
			f.format("%u\n)")
		}
	}

	lister{"\n\n", "\n\n", file.Ast.(*list.List), "", ""}.src(f)

}

func (n LR) bin(f *formatter, op string) {
	n.Left.(sourcer).src(f)
	f.format(op)
	n.Right.(sourcer).src(f)
}

// Str prints 'file' to 'f'. This function prints the source code form, not the AST form.
func (file *File) Str(f strutil.Formatter) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = e.(error)
		}
	}()

	file.src(&formatter{f})
	return
}

// me reports its calling site as a string.
func me() string {
	_, file, line, ok := runtime.Caller(1)
	if !ok {
		return "???"
	}

	return path.Base(file) + ":" + strconv.Itoa(line)
}
